﻿package we3d.scene 
{
	import flash.utils.Dictionary;
	
	import we3d.View3d;
	import we3d.core.Object3d;
	import we3d.core.Camera3d;
	import we3d.layer.Layer;
	import we3d.material.Surface;
	import we3d.math.Vector3d;
	import we3d.math.Matrix3d;
	import we3d.mesh.Vertex;
	import we3d.mesh.Face;
	import we3d.renderer.IObjectRenderer;
	import we3d.renderer.cc.CCDefault;
	import we3d.renderer.IPolyRenderer;
	import we3d.renderer.ZSort;
	
	/** 
	* SceneObjects are the mesh objects in a 3d scene
	*/
	public class SceneObject extends Object3d 
	{
		public function SceneObject() {}
		
		/**
		* Used by CustomCull renderer for polygon rendering
		*/
		public var objectRenderer:IObjectRenderer = new CCDefault;
		/**
		* Used for polygon sorting
		*/
		public var polyRenderer:IPolyRenderer = new ZSort();
		/** 
		* Points of the object (Vertex) 
		*/
		public var points:Array=[];
		/** 
		* Polygons of the object (Face)
		*/
		public var polygons:Array=[];
		/**
		* @private
		*/
		public var layer:Dictionary;
		
		/**
		* Set the 2d layer for the scene object. It's possible to set more layers for different views if multiple views are used with the same SceneObjects
		* @param	scene	the scene wich provides the layer
		* @param	lyr		the layer were the object should be rendered
		*/
		public function setLayer (view:View3d, lyr:Layer) :void {
			if(layer == null) layer = new Dictionary(true);
			layer[view] = lyr;
		}
		
		/** 
		* Add a point to the Object's point list
		* @param	x
		* @param	y
		* @param	z
		* @param	testPoints	if true test if the point is already available
		* @return The id of the point in the points list
		*/
		public function addPoint (x:Number=0, y:Number=0, z:Number=0, testPoints:Boolean=false) :uint {
			
			if(testPoints) {
				var p:Vertex;
				var L:int = points.length
				for(var i:int = 0; i<L; i++) {
					p = points[i];
					if(p.x == x && p.y == y && p.z == z) {
						return i;
					}
				}
			}
			
			objectCuller.testPoint(x, y, z);
			
			return points.push(new Vertex(x,y,z)) - 1;
		}
		
		/**
		* Add a Vertex to the SceneObject
		* @param	v the Vertex to add
		* @return The id of the point in the points list
		*/
		public function addVertex (v:Vertex) :uint {
			var id:int = points.indexOf(v);
			if(id != -1) return id;
			objectCuller.testPoint(v.x, v.y, v.z);
			return points.push(v)-1;
		}
		
		/**
		* Calculate point normals for smooth lighting
		*/
		public function calculatePointNormals () :void {
			var pts:Array = [];
			
			var ax:Number;
			var ay:Number;
			var az:Number;
			var c:Number;
			
			var L:int =  points.length;
			var i:int;
			var j:int;
			var k:int;
			var plg:Face;
			var plgs:int = polygons.length;
			var p:Vertex;
			
			for(i=0; i<L; i++) {
				
				ax = ay = az = c = 0;
				p = points[i];
				
				for(j=0; j<plgs; j++) {
					
					plg = polygons[j];
					
					for(k=0; k<plg.vtxs.length; k++) {
						if(plg.vtxs[k] == p) {
							ax += plg.normal.x;
							ay += plg.normal.y;
							az += plg.normal.z;
							c++;
						}
					}
				}
				
				ax /= c;
				ay /= c;
				az /= c;
				if(p.normal == null) p.normal = new Vector3d();
				
				c = Math.sqrt(ax*ax + ay*ay + az*az);
				p.normal.assign(ax/c, ay/c, az/c);
			}
		}
		
		/**
		* Add a new polygon to the SceneObject 
		* 
		* <code><pre>
		* 	import we3d.scene.SceneObject;
		* 	import we3d.material.Surface;
		* 	
		* 	var obj:SceneObject = new SceneObject();
		* 
		* 	obj.addPoint(0,0,0);
		* 	obj.addPoint(100,0,0);
		* 	obj.addPoint(-100,0,0);
		* 
		* 	var sf:Surface = new Surface();
		* 
		* 	obj.addPolygon(sf, 0, 1, 2);
		* 	obj.addPolygon(sf, [2, 1, 0]);
		* </pre></code>
		*  
		* @param	surface	surface of the polygon
		* @param	... verts	vertices of the polygon
		* @return the id in the polygons array
		*/
		public function addPolygon (surface:Surface, ...verts:Array) :Face {
			
			var pts:Array = [];
			var i:int;
			var L:int;
			
			if(verts[0] is Array) {
				var a:Array = verts[0];
				L = a.length;
				for(i=0; i<L; i++) {
					pts.push(points[a[i]]);
				}
			}else{
				L = verts.length;
				for(i=0; i<L; i++) {
					pts.push(points[verts[i]]);
				}
			}
			
			var fc:Face = new Face();
			fc.surface = surface;
			fc.vtxs = pts;
			fc.init(this);
			
			polygons.push(fc);
			
			return fc;
		}
		
		/**
		* Returns the number of points in the object
		*/
		public function get numPoints () :int {
			return points.length;
		}
		
		/**
		* Returns the number of polygons in the object
		*/
		public function get numPolygons () :int {
			return polygons.length;
		}
		
		/**
		* Returns a polygon from the polygons array
		* @param	id the array id
		* @return	a polygon
		*/
		public function polygonAt (id:int) :Face {
			return polygons[id];
		}
		
		/**
		* Returns a point from the points array
		* @param	id the array id
		* @return	a point
		*/
		public function pointAt (id:int) :Vertex {
			return points[id];
		}
		
		/**
		* Returns the id of a point
		* @param	v the point
		* @return	array id
		*/
		public function getPointId (v:Vertex) :int {
			return points.indexOf(v);
		}
		
		/**
		* Returns the id of a polygon
		* @param	f the polygon
		* @return	array id
		*/
		public function getPolygonId (f:Face) :int {
			return polygons.indexOf(f);
		}
		
		/**
		* This method have to be called if a bitmap texture get a new width or height and is already assigned to polygons. 
		* It is not required if only the bitmap changes but the size of the bitmaps is the same. 
		* @param	sf	The surface with the bitmap texture
		*/
		public function initTextureSizeOnPolygons (sf:Surface) :void {
			var L:int = polygons.length;
			for (var i:int=0; i<L; i++) {
				if(polygons[i].surface === sf) {
					sf.initFace(polygons[i]);
				}
			}
		}
		
		/**
		* @private
		*/
		public function initMesh (view:View3d, cgv:Matrix3d) :Boolean {
			
			var lyr:Layer;
			
			if(layer) {
				lyr = layer[view] || view.firstLayer;
			}else{
				lyr = view.firstLayer;
			}
			
			lyr.cd = cgv.c;
			lyr.ch = cgv.g;		
			lyr.cl = cgv.k;		
			lyr.cp = cgv.o;
			
			view.currentLayer = lyr;
			
			return false;
		}
		
		/**
		* Projects all points to 2d
		* @param	cam camera for projection
		* @param	cgv view matrix
		*/
		public function projectPoints (cam:Camera3d, cgv:Matrix3d) :void {
			
			var _p:Array = points;
			var L:int = _p.length;
			
			var v:Vertex;
			var _w:Number = cam.t;	var _h:Number = cam.s;
			var x:Number;	var y:Number;	var z:Number;
			var a:Number = cgv.a;	var b:Number = cgv.b;	var c:Number = cgv.c;
			var e:Number = cgv.e;	var f:Number = cgv.f;	var g:Number = cgv.g;
			var i:Number = cgv.i;	var j:Number = cgv.j;	var k:Number = cgv.k;
			var m:Number = cgv.m;	var n:Number = cgv.n;	var o:Number = cgv.o + cam._nearClipping;
			
			for(var _i:int=0; _i<L; _i++) {
				v = _p[_i];
				x = v.x;	y = v.y; 	z = v.z;
				v.wz = c*x + g*y + k*z + o;
				v.wy = b*x + f*y + j*z + n;
				v.wx = a*x + e*y + i*z + m;
				
				if(v.wz>0) {
					v.sy = _h - v.wy/v.wz * _h;
					v.sx = _w + v.wx/v.wz * _w;
				}else{
					v.sy = _h - v.wy * _h;
					v.sx = _w + v.wx * _w;
				}
			}
		}
		
	}
}